/**********************************************************************************************************************
 *
 * Project:    mGameEngine
 * File:       tcpconnection.cpp
 * Author:     karooolek
 * Created on: 2009-03-14
 *
 **********************************************************************************************************************/

#include "tcpconnection.h"

namespace mGameEngine
{
namespace Network
{

TCPConnection::TCPConnection() :
    IPConnection()
{
}

TCPConnection::TCPConnection(const IPAddress &ipaddr) :
    IPConnection(ipaddr)
{
}

TCPConnection::~TCPConnection()
{
    close();
}

void TCPConnection::open()
{
    // already opened
    if(_state == CONN_STATE_OPENED)
    {
        return;
    }

    // close previous connection
    close();
    
    // create socket
    _socket = socket(PF_INET, SOCK_STREAM, 0);
    if(_socket == INVALID_SOCKET)
    {
        return;
    }
    uint8 optVal = 1;
    setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&optVal, sizeof(optVal));

    // bind socket
    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(_ipaddr.port);
    addr.sin_addr.s_addr = INADDR_ANY;
    if(bind(_socket, (sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR)
    {
        close();
        return;
    }

    // listen
    if(listen(_socket, SOMAXCONN) == SOCKET_ERROR)
    {
        close();
        return;
    }

    // set temporary state
    _state = CONN_STATE_OPENED;

    // wait for connection and bind new socket
    SOCKET newSocket;
    sockaddr_in newAddr;
    int newAddrSize = sizeof(newAddr);
    newSocket = accept(_socket, (sockaddr *)&newAddr, (socklen_t *)&newAddrSize);
    if(newSocket == INVALID_SOCKET)
    {
        close();
        return;
    }

#ifdef WIN32
    closesocket(_socket);
#else
    ::close(_socket);
#endif
    _socket = newSocket;

    // TODO get hostname and save in _ipaddr

    // set connection state
    _state = CONN_STATE_CONNECTED;
}

void TCPConnection::close()
{
    // unbind socket
    if(_socket != INVALID_SOCKET)
    {
#ifdef WIN32
        closesocket( _socket);
#else
        ::close(_socket);
#endif
    }
    _socket = INVALID_SOCKET;

    // change state
    _state = CONN_STATE_CLOSED;
}

void TCPConnection::connect()
{
    // close previous connection
    close();

    // create socket
    _socket = socket(PF_INET, SOCK_STREAM, 0);
    if(_socket == INVALID_SOCKET)
    {
        return;
    }

    // set temporary state
    _state = CONN_STATE_CONNECTING;

    // connect
    // TODO is it the best way???
    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(_ipaddr.port);
    addr.sin_addr.s_addr = inet_addr(_ipaddr.hostname.c_str());
    if(::connect(_socket, (const sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR)
    {
        close();
        return;
    }

    // set state
    _state = CONN_STATE_CONNECTED;
}

void TCPConnection::disconnect()
{
    close();
}

uint TCPConnection::send(const uint8 *data, uint size)
{
    // not connected
    if(_state != CONN_STATE_CONNECTED)
    {
        return 0;
    }

    // send data
    uint res = ::send(_socket, (const char *)data, size, 0);
    if(res == SOCKET_ERROR)
    {
        // TODO check errors
    }
    
    // return sent bytes
    return res;
}

uint TCPConnection::receive(uint8 *buffer, uint size)
{
    // not connected
    if(_state != CONN_STATE_CONNECTED)
    {
        return 0;
    }

    // receive data
    uint res = ::recv(_socket, (char *)buffer, size, 0);

    // connection lost
    if(res == 0 || res == SOCKET_ERROR)
    {
        close();
        _state = CONN_STATE_LOST;
        return 0;
    }

    // return received bytes
    return res;
}

}
}

